Skip to content
🌊海洋蓝
🌸樱花粉
🍃森林绿
🔮幻夜紫
🌙暗夜黑

HarmonyOS 水印添加能力

项目地址:https://gitcode.com/harmonyos_samples/watermark 技术栈:HarmonyOS / ArkTS / ArkUI

项目简介

在移动应用开发中,水印功能是一个常见且实用的需求。无论是保护版权、标识来源,还是防止信息泄露,水印都扮演着重要角色。本项目是 HarmonyOS 官方提供的水印添加能力示例,全面展示了在 HarmonyOS 生态中实现水印功能的多种技术方案。

该项目涵盖了四大核心场景:页面背景水印图片水印拍照水印PDF 水印。通过 Canvas 绘制、OffscreenCanvas 离屏渲染、PDFKit 服务等技术,为开发者提供了一套完整的水印解决方案。项目代码结构清晰,注释完善,非常适合作为 HarmonyOS 水印开发的参考实现。

功能概览

  • 页面背景水印(Stack 方式):使用 Stack 层叠布局将水印组件与页面内容融合
  • 页面背景水印(Overlay 方式):使用 overlay 浮层属性实现水印覆盖
  • 保存图片添加水印:对本地图片进行水印处理并保存到系统图库
  • 拍照图片添加水印:调用系统相机拍照后自动添加水印
  • PDF 添加水印:使用 PDFKit 服务为 PDF 文档添加文字水印

首页截图

上图展示了应用的首页,包含页面背景、图片、文件三大分类的水印功能入口。

工程结构

text
entry/src/main/ets/
├── component
│   ├── NavBar.ets                  // 顶部导航条组件
│   └── Watermark.ets               // 页面水印核心组件
├── constants
│   ├── Utils.ets                   // 工具类:水印绘制、图片保存
│   └── Constants.ets               // 公共常量类
├── entryability
│   └── EntryAbility.ets            // 程序入口类
└── pages
    ├── Index.ets                   // 首页:功能导航
    ├── CameraPage.ets              // 拍照添加水印
    ├── SaveImagePage.ets           // 保存图片添加水印
    ├── WatermarkPdfPage.ets        // PDF 文件添加水印
    ├── WatermarkStackPage.ets      // Stack 方式页面水印
    └── WatermarkOverlayPage.ets    // Overlay 方式页面水印

核心实现解析

模块一:Canvas 水印组件(Watermark.ets)

水印组件是整个项目的核心,它基于 ArkUI 的 Canvas 组件实现,通过 2D 绘图上下文在画布上绘制重复的文字水印。

typescript
@Component
export struct Watermark {
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  
  // 可配置的水印属性
  @Prop watermarkWidth: number = 120;      // 水印单元宽度
  @Prop watermarkHeight: number = 120;     // 水印单元高度
  @Prop watermarkText: string = '水印';     // 水印文字内容
  @Prop rotationAngle: number = -30;       // 旋转角度(度)
  @Prop fillColor: string = '#10000000';   // 填充颜色(带透明度)
  @Prop font: string = '16vp';             // 字体大小

  draw() {
    this.context.fillStyle = this.fillColor;
    this.context.font = this.font;
    
    // 计算需要绘制的行列数
    const colCount = Math.ceil(this.context.width / this.watermarkWidth);
    const rowCount = Math.ceil(this.context.height / this.watermarkHeight);
    
    // 双重循环绘制网格状水印
    for (let col = 0; col <= colCount; col++) {
      let row = 0;
      for (; row <= rowCount; row++) {
        const angle = this.rotationAngle * Math.PI / 180;
        this.context.rotate(angle);
        
        // 根据旋转角度计算偏移位置
        const positionX = this.rotationAngle > 0 ? this.watermarkHeight * Math.tan(angle) : 0;
        const positionY = this.rotationAngle > 0 ? 0 : this.watermarkWidth * Math.tan(-angle);
        
        this.context.fillText(this.watermarkText, positionX, positionY);
        this.context.rotate(-angle);
        this.context.translate(0, this.watermarkHeight);
      }
      // 换列绘制
      this.context.translate(0, -this.watermarkHeight * row);
      this.context.translate(this.watermarkWidth, 0);
    }
  }

  build() {
    Canvas(this.context)
      .width('100%')
      .height('100%')
      .hitTestBehavior(HitTestMode.Transparent)  // 透明穿透,不影响下层交互
      .onReady(() => this.draw())
  }
}

技术要点解析:

  1. CanvasRenderingContext2D:使用 HarmonyOS 提供的 2D 绘图上下文,支持文字绘制、旋转、平移等操作
  2. hitTestBehavior(HitTestMode.Transparent):设置透明穿透,确保水印层不会拦截下层组件的触摸事件
  3. 网格绘制算法:通过双重循环计算行列位置,配合 translate 变换实现满屏水印效果
  4. 旋转计算:将角度转换为弧度,使用三角函数计算旋转后的位置偏移

模块二:Stack 层叠布局水印(WatermarkStackPage.ets)

Stack 方式通过层叠布局将水印组件覆盖在页面内容之上,是最直观的页面水印实现方式。

typescript
@Entry
@Component
struct CanvasPage {
  @State angle: number = 0;

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
      NavBar()
      Stack({ alignContent: Alignment.Center }) {
        Column() {
          Image($r('app.media.empty'))
            .width(110)
            .height(88)
            .rotate({ angle: this.angle })
            .onClick(() => {
              this.angle = this.angle === 0 ? 180 : 0;
            })
        }
        Watermark({ rotationAngle: 20 })  // 水印层覆盖在内容之上
      }
      .layoutWeight(1)
      .width('100%')
    }
    .width('100%')
    .height('100%')
  }
}

Stack 水印效果

上图展示了使用 Stack 组件添加的页面水印效果,水印以 20 度倾斜角均匀分布在页面上。

模块三:Overlay 浮层水印(WatermarkOverlayPage.ets)

Overlay 方式使用 ArkUI 的 overlay 属性,通过 @Builder 构建水印浮层,实现更灵活的水印覆盖。

typescript
@Entry
@Component
struct OverlayPage {
  @State angle: number = 0;

  // 使用 @Builder 构建水印浮层
  @Builder
  watermarkBuilder() {
    Column() {
      Watermark()
    }
    .hitTestBehavior(HitTestMode.Transparent)
  }

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
      NavBar()
      Column() {
        Image($r('app.media.empty'))
          .width(110)
          .height(88)
      }
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .layoutWeight(1)
      .overlay(this.watermarkBuilder())  // 通过 overlay 属性挂载水印
    }
    .width('100%')
    .height('100%')
  }
}

Overlay 水印效果

上图展示了使用 overlay 属性添加的页面水印效果,与 Stack 方式相比,overlay 更加轻量且易于控制层级。

Stack vs Overlay 对比:

特性Stack 方式Overlay 方式
实现复杂度简单中等
层级控制通过子组件顺序通过 overlay 属性
灵活性适合固定水印适合动态水印
性能良好更优(无需额外容器)

模块四:图片水印处理(SaveImagePage.ets + Utils.ets)

图片水印是项目中技术最复杂的部分,涉及图片解码、PixelMap 操作、离屏渲染和文件保存等多个环节。

核心工具函数 addWatermark

typescript
export function addWatermark(
  imagePixelMap: ImagePixelMap,
  text: string = 'watermark',
  drawWatermark?: (OffscreenContext: OffscreenCanvasRenderingContext2D) => void
): image.PixelMap {
  // 将像素尺寸转换为 vp 单位
  const height = uiContext?.px2vp(imagePixelMap.height) as number;
  const width = uiContext?.px2vp(imagePixelMap.width) as number;
  
  // 创建离屏画布
  const offScreenCanvas = new OffscreenCanvas(width, height);
  const offScreenContext = offScreenCanvas.getContext('2d');
  
  // 绘制原始图片
  offScreenContext.drawImage(imagePixelMap.pixelMap, 0, 0, width, height);
  
  // 绘制水印(支持自定义绘制逻辑)
  if (drawWatermark) {
    drawWatermark(offScreenContext);
  } else {
    // 默认水印样式:右下角半透明文字
    const displayWidth = display.getDefaultDisplaySync().width;
    const vpWidth = uiContext?.px2vp(displayWidth) ?? displayWidth;
    const imageScale = width / vpWidth;
    
    offScreenContext.textAlign = 'right';
    offScreenContext.fillStyle = '#A2FFFFFF';  // 半透明白色
    offScreenContext.font = 12 * imageScale + 'vp';
    const padding = 5 * imageScale;
    offScreenContext.fillText(text, width - padding, height - padding);
  }
  
  // 返回带水印的 PixelMap
  return offScreenContext.getPixelMap(0, 0, width, height);
}

图片保存流程:

typescript
export async function saveToFile(pixelMap: image.PixelMap, context: Context): Promise<void> {
  // 1. 获取图库访问助手
  const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
  
  // 2. 创建图片资源
  const filePath = await phAccessHelper.createAsset(
    photoAccessHelper.PhotoType.IMAGE, 
    'png'
  );
  
  // 3. 使用 ImagePacker 编码为 PNG
  const imagePacker = image.createImagePacker();
  const imageBuffer = await imagePacker.packToData(pixelMap, {
    format: 'image/png',
    quality: 100
  });
  
  // 4. 写入文件
  const mode = fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE;
  const fd = (await fileIo.open(filePath, mode)).fd;
  await fileIo.truncate(fd);
  await fileIo.write(fd, imageBuffer);
  fileIo.close(fd);
}

图片添加水印前

上图展示了原始图片页面,底部有"添加水印"按钮。

图片添加水印后

上图展示了添加水印后的效果,右下角出现了半透明的"水印水印水印"文字。点击保存按钮可将带水印图片存入系统图库。

技术要点解析:

  1. OffscreenCanvas:离屏画布在后台进行绘制操作,不会阻塞主线程,适合图片处理等耗时操作
  2. PixelMap:HarmonyOS 的图片数据载体,支持像素级操作
  3. ImagePacker:图片编码器,支持 PNG、JPEG 等格式输出
  4. photoAccessHelper:系统图库访问助手,用于保存图片到相册
  5. px2vp 转换:将像素单位转换为 vp(虚拟像素),确保在不同分辨率设备上显示一致

模块五:PDF 水印(WatermarkPdfPage.ets)

PDF 水印使用 HarmonyOS 提供的 PDFKit 服务实现,支持文字水印的添加、预览和保存。

typescript
// 配置水印信息
getWatermarkInfo() {
  const watermarkInfo: pdfService.TextWatermarkInfo = new pdfService.TextWatermarkInfo();
  watermarkInfo.watermarkType = pdfService.WatermarkType.WATERMARK_TEXT;
  watermarkInfo.content = 'This is Watermark';
  watermarkInfo.textSize = 32;
  watermarkInfo.textColor = 200;    // 灰色
  watermarkInfo.rotation = 45;      // 45度旋转
  watermarkInfo.opacity = 0.3;      // 30% 透明度
  return watermarkInfo;
}

// 添加水印到 PDF
addWatermark() {
  const filePath = this.getPdfSandboxPath();
  let pdfDocument: pdfService.PdfDocument = new pdfService.PdfDocument();
  pdfDocument.loadDocument(filePath);
  
  // 为所有页面添加水印
  pdfDocument.addWatermark(
    this.getWatermarkInfo(), 
    0,                              // 起始页
    pdfDocument.getPageCount(),     // 结束页
    true,                           // 前景
    true                            // 覆盖所有页面
  );
  
  // 保存带水印的 PDF
  const watermarkFilePath = this.getAddedWatermarkPdfSandboxPath();
  pdfDocument.saveDocument(watermarkFilePath);
  this.showInPdfView(watermarkFilePath);
}

PDF 添加水印前

上图展示了 PDF 预览页面,底部有"添加水印"按钮。

PDF 添加水印后

上图展示了添加水印后的 PDF 效果,页面上出现了倾斜的半透明文字水印。

技术要点解析:

  1. PdfView 组件:HarmonyOS 提供的 PDF 预览组件,支持页面缩放、滚动等交互
  2. PdfController:PDF 控制器,负责文档加载、页面跳转、释放等操作
  3. PdfDocument:PDF 文档对象,提供添加水印、合并、拆分等操作
  4. TextWatermarkInfo:文字水印配置对象,支持内容、大小、颜色、旋转、透明度等属性
  5. 沙箱路径:PDF 文件先存入应用沙箱,处理后再通过文件选择器保存到用户指定位置

运行效果

项目提供了完整的运行效果展示,以下是各功能模块的实际截图:

功能截图
首页导航首页
Stack 页面水印Stack水印
Overlay 页面水印Overlay水印
图片水印(添加前)图片前
图片水印(添加后)图片后
PDF 水印(添加前)PDF前
PDF 水印(添加后)PDF后

动态效果展示

项目还提供了水印效果的动态演示:

水印动画演示

上图展示了页面水印的动态效果,可以看到水印以倾斜角度均匀分布在页面上。

关键技术总结

1. Canvas 2D 绘图

HarmonyOS 提供了完整的 Canvas 2D 绘图能力,支持:

  • 文字绘制(fillTextstrokeText
  • 几何变换(rotatetranslatescale
  • 样式设置(fillStylefonttextAlign
  • 离屏渲染(OffscreenCanvas

2. 图片处理流程

图片水印的处理流程为:

图片资源 → ImageSource → PixelMap → OffscreenCanvas 绘制 → PixelMap → ImagePacker 编码 → 文件保存

3. PDF 水印流程

PDF 水印的处理流程为:

PDF 资源 → 沙箱存储 → PdfDocument 加载 → addWatermark → 沙箱保存 → PdfView 预览 → 用户保存

4. 权限说明

本项目涉及以下系统权限:

  • ohos.permission.WRITE_IMAGEVIDEO:保存图片到系统图库(需要动态申请)

总结

本项目作为 HarmonyOS 官方水印能力示例,全面展示了在 HarmonyOS 生态中实现水印功能的多种技术方案。无论是页面水印、图片水印还是 PDF 水印,都提供了完整的实现代码和详细的注释说明。

适用场景:

  • 需要为应用添加版权保护水印的开发者
  • 学习 HarmonyOS Canvas 绘图和图像处理的初学者
  • 需要实现 PDF 文档水印功能的企业应用

学习建议:

  1. 先理解 Watermark.ets 中 Canvas 绘制的核心逻辑
  2. 对比 Stack 和 Overlay 两种页面水印的实现差异
  3. 重点学习 Utils.ets 中图片处理的完整流程
  4. 参考 PDF 水印实现,了解 PDFKit 服务的使用方法

通过本项目的学习,开发者可以快速掌握在 HarmonyOS 平台上实现各类水印功能的技术要点,并能够根据实际需求进行扩展和定制。

Released under the MIT License.